#include "gstreamer_5.h"

typedef struct _CustomData
{
    GstElement *playbin;

    GtkWidget *sink_widget;
    GtkWidget *slider;
    GtkWidget *streams_list;
    gulong slider_update_signal_id;

    GstState state;
    gint64 duration;

} CustomData;

static void delete_event_cb(GtkWidget *, GdkEvent *, CustomData *);

static void create_ui(CustomData *);

static gboolean refresh_ui(CustomData *);

static void stop_cb(GtkButton *, CustomData *);

static void error_cb(GstBus *, GstMessage *, CustomData *);

static void eos_cb(GstBus *, GstMessage *, CustomData *);

static void state_change_cb(GstBus *, GstMessage *, CustomData *);

static void application_cb(GstBus *, GstMessage *, CustomData *);

static void play_cb(GtkButton *, CustomData *);

static void pause_cb(GtkButton *, CustomData *);

static void slider_cb(GtkRange *, CustomData *);

static void analyze_streams(CustomData *);

static void tags_cb(GstElement *, gint, CustomData *);

int play_04(int argc, char *argv[])
{
    CustomData data;
    GstBus *bus;
    GstMessage *message;
    GstStateChangeReturn ret;
    GstElement *gtkglsink, *videosink;

    data.duration = GST_CLOCK_TIME_NONE;

    gst_init(&argc, &argv);
    gtk_init(&argc, &argv);

    data.playbin = gst_element_factory_make("playbin", "playbin");
    videosink = gst_element_factory_make("glsinkbin", "glsinkbin");
    gtkglsink = gst_element_factory_make("gtkglsink", "gtkglsink");

    if (gtkglsink != nullptr && videosink != nullptr)
    {
        g_print("Successfully created GTK GL Sink\n");

        g_object_set(videosink, "sink", gtkglsink, nullptr);
        g_object_get(gtkglsink, "widget", &data.sink_widget, nullptr);
    }
    else
    {
        g_print("Could not create gtkglsink, falling back to gtksink.\n");

        videosink = gst_element_factory_make("gtksink", "gtksink");
        g_object_get(videosink, "widget", &data.sink_widget, nullptr);
    }

    if (!data.playbin || !videosink)
    {
        g_printerr("Not all elements could be created!\n");
        return -1;
    }

    g_object_set(data.playbin, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", nullptr);

    g_object_set(data.playbin, "video-sink", videosink, nullptr);

    g_signal_connect(G_OBJECT(data.playbin), "video-tags-changed", (GCallback)tags_cb, &data);
    g_signal_connect(G_OBJECT(data.playbin), "audio-tags-changed", (GCallback)tags_cb, &data);
    g_signal_connect(G_OBJECT(data.playbin), "text-tags-changed", (GCallback)tags_cb, &data);

    create_ui(&data);

    bus = gst_element_get_bus(data.playbin);
    gst_bus_add_signal_watch(bus);
    g_signal_connect(GST_OBJECT(bus), "message::error", (GCallback)error_cb, &data);
    g_signal_connect(GST_OBJECT(bus), "message::eos", (GCallback)eos_cb, &data);
    g_signal_connect(GST_OBJECT(bus), "message::state-changed", (GCallback)state_change_cb, &data);
    g_signal_connect(GST_OBJECT(bus), "message::application", (GCallback)application_cb, &data);
    gst_object_unref(bus);

    ret = gst_element_set_state(data.playbin, GST_STATE_PLAYING);
    if (ret == GST_STATE_CHANGE_FAILURE)
    {
        g_printerr("Unable to set the pipeline to playing state.\n");
        g_object_unref(data.playbin);
        return -1;
    }

    g_timeout_add_seconds(1, (GSourceFunc)refresh_ui, &data);

    gtk_main();

    gst_element_set_state(data.playbin, GST_STATE_NULL);
    gst_object_unref(data.playbin);

    return 0;
}

static void create_ui(CustomData *data)
{
    GtkWidget *main_window;
    GtkWidget *main_box;
    GtkWidget *main_hbox;
    GtkWidget *controls;
    GtkWidget *play_button, *pause_button, *stop_button;

    main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    g_signal_connect(G_OBJECT(main_window), "delete-event", G_CALLBACK(delete_event_cb), data);

    play_button = gtk_button_new_from_icon_name("media-playback-start", GTK_ICON_SIZE_SMALL_TOOLBAR);
    g_signal_connect(G_OBJECT(play_button), "clicked", G_CALLBACK(play_cb), data);

    pause_button = gtk_button_new_from_icon_name("meida-playback-pause", GTK_ICON_SIZE_SMALL_TOOLBAR);
    g_signal_connect(G_OBJECT(pause_button), "clicked", G_CALLBACK(pause_cb), data);

    stop_button = gtk_button_new_from_icon_name("meida-playback-stop", GTK_ICON_SIZE_SMALL_TOOLBAR);
    g_signal_connect(G_OBJECT(stop_button), "clicked", G_CALLBACK(stop_cb), data);

    data->slider = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0, 100, 1);
    gtk_scale_set_draw_value(GTK_SCALE(data->slider), 0);
    data->slider_update_signal_id = g_signal_connect(G_OBJECT(data->slider), "value-changed", G_CALLBACK(slider_cb), data);

    data->streams_list = gtk_text_view_new();
    gtk_text_view_set_editable(GTK_TEXT_VIEW(data->streams_list), false);

    controls = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_pack_start(GTK_BOX(controls), play_button, false, false, 2);
    gtk_box_pack_start(GTK_BOX(controls), pause_button, false, false, 2);
    gtk_box_pack_start(GTK_BOX(controls), stop_button, false, false, 2);
    gtk_box_pack_start(GTK_BOX(controls), data->slider, true, true, 2);

    main_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_pack_start(GTK_BOX(main_hbox), data->sink_widget, true, true, 0);
    gtk_box_pack_start(GTK_BOX(main_hbox), data->streams_list, false, false, 2);

    main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    gtk_box_pack_start(GTK_BOX(main_box), main_hbox, true, true, 0);
    gtk_box_pack_start(GTK_BOX(main_box), controls, false, false, 0);
    gtk_container_add(GTK_CONTAINER(main_window), main_box);
    gtk_window_set_default_size(GTK_WINDOW(main_window), 640, 480);

    gtk_widget_show_all(main_window);
}

static gboolean refresh_ui(CustomData *data)
{
    gint64 current = -1;

    if (data->state < GST_STATE_PAUSED)
    {
        return true;
    }

    if (!GST_CLOCK_TIME_IS_VALID(data->duration))
    {
        if (gst_element_query_duration(data->playbin, GST_FORMAT_TIME, &data->duration))
        {
            g_printerr("Could not query current duration.\n");
        }
        else
        {
            gtk_range_set_range(GTK_RANGE(data->slider), 0, (gdouble)data->duration / GST_SECOND);
        }
    }

    if (gst_element_query_position(data->playbin, GST_FORMAT_TIME, &current))
    {
        g_signal_handler_block(data->slider, data->slider_update_signal_id);
        gtk_range_set_value(GTK_RANGE(data->slider), (gdouble)current / GST_SECOND);
        g_signal_handler_unblock(data->slider, data->slider_update_signal_id);
    }

    return true;
}

static void delete_event_cb(GtkWidget *widget, GdkEvent *event, CustomData *data)
{
    stop_cb(nullptr, data);
    gtk_main_quit();
}

static void stop_cb(GtkButton *button, CustomData *data)
{
    gst_element_set_state(data->playbin, GST_STATE_NULL);
}

static void error_cb(GstBus *bus, GstMessage *message, CustomData *data)
{
    GError *err;
    gchar *debug_info;

    gst_message_parse_error(message, &err, &debug_info);
    g_printerr("Error received from element %s: %s.\n", GST_OBJECT_NAME(message->src), err->message);
    g_printerr("Debuging information: %s.\n", debug_info ? debug_info : "none");
    g_clear_error(&err);
    g_free(debug_info);

    gst_element_set_state(data->playbin, GST_STATE_READY);
}

static void eos_cb(GstBus *bus, GstMessage *message, CustomData *data)
{
    g_print("End-Of-Stream reached.\n");
    gst_element_set_state(data->playbin, GST_STATE_READY);
}

static void state_change_cb(GstBus *bus, GstMessage *message, CustomData *data)
{
    GstState old_state, new_state, pending_state;
    gst_message_parse_state_changed(message, &old_state, &new_state, &pending_state);
    if (GST_MESSAGE_SRC(message) == GST_OBJECT(data->playbin))
    {
        data->state = new_state;
        g_print("State set to %s.\n", gst_element_state_get_name(new_state));
        if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED)
        {
            refresh_ui(data);
        }
    }
}

static void application_cb(GstBus *bus, GstMessage *message, CustomData *data)
{
    if (g_strcmp0(gst_structure_get_name(gst_message_get_structure(message)), "tags-changed") == 0)
    {
        analyze_streams(data);
    }
}

static void play_cb(GtkButton *button, CustomData *data)
{
    gst_element_set_state(data->playbin, GST_STATE_PLAYING);
}

static void pause_cb(GtkButton *button, CustomData *data)
{
    gst_element_set_state(data->playbin, GST_STATE_PAUSED);
}

static void slider_cb(GtkRange *range, CustomData *data)
{
    gdouble value = gtk_range_get_value(GTK_RANGE(data->slider));
    gst_element_seek_simple(data->playbin, GST_FORMAT_TIME, (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), (gint64)(value * GST_SECOND));
}

static void analyze_streams(CustomData *data)
{
    gint i;
    GstTagList *tags;
    gchar *str, *total_str;
    guint rate;
    gint n_video, n_audio, n_text;
    GtkTextBuffer *text;

    text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(data->streams_list));
    gtk_text_buffer_set_text(text, "", -1);

    g_object_get(data->playbin, "n-video", &n_video, nullptr);
    g_object_get(data->playbin, "n-audio", &n_audio, nullptr);
    g_object_get(data->playbin, "n-text", &n_text, nullptr);

    for (i = 0; i < n_video; i++)
    {
        tags = NULL;

        g_signal_emit_by_name(data->playbin, "get-video-tags", i, &tags);
        if (tags)
        {
            total_str = g_strdup_printf("video stream %d: \n", i);
            gtk_text_buffer_insert_at_cursor(text, total_str, -1);
            g_free(total_str);

            gst_tag_list_get_string(tags, GST_TAG_VIDEO_CODEC, &str);
            total_str = g_strdup_printf("  codec: %s\n", str ? str : "unknown");
            gtk_text_buffer_insert_at_cursor(text, total_str, -1);
            g_free(total_str);
            g_free(str);
            gst_tag_list_free(tags);
        }
    }

    for (i = 0; i < n_audio; i++)
    {
        tags = NULL;

        g_signal_emit_by_name(data->playbin, "get-audio-tags", i, &tags);
        if(tags) {
            total_str = g_strdup_printf("\naudio stream %d:\n", i);
            gtk_text_buffer_insert_at_cursor(text, total_str, -1);
            g_free(total_str);

            if(gst_tag_list_get_string(tags, GST_TAG_AUDIO_CODEC, &str)) {
                total_str = g_strdup_printf("   codec: %s\n", str);
                gtk_text_buffer_insert_at_cursor(text, total_str, -1);
                g_free(total_str);
                g_free(str);
            }

            if(gst_tag_list_get_string(tags, GST_TAG_LANGUAGE_CODE, &str)) {
                total_str = g_strdup_printf("   language: %s\n", str);
                gtk_text_buffer_insert_at_cursor(text, total_str, -1);
                g_free(total_str);
                g_free(str);
            }

            if(gst_tag_list_get_uint(tags, GST_TAG_BITRATE, &rate)) {
                total_str = g_strdup_printf("   bitrate: %d\n", rate);
                gtk_text_buffer_insert_at_cursor(text, total_str, -1);
                g_free(total_str);
            }


            gst_tag_list_free(tags);
        }
    }
}

static void tags_cb(GstElement * playbin, gint stream, CustomData * data) {
    gst_element_post_message(playbin, gst_message_new_application(GST_OBJECT(playbin), gst_structure_new_empty("tags-changed")));
}